package com.blue.http.annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;

import com.blue.core.dict.Charset;
import com.blue.core.dict.HttpMethod;
import com.blue.http.config.ErrorConfig;
import com.blue.http.message.HttpRequest;
import com.blue.http.message.UploadFile;


/**
 * HTTP解析类
 * 
 * @author zhengj
 * @since 1.0 2017年3月8日
 */
public class HttpParser
{
	private static Logger logger = LoggerFactory.getLogger(HttpParser.class);
	private static Set<Class<?>> paramSet = new HashSet<>();
	private static String paramString;

	private ApplicationContext applicationContext;
	private ErrorConfig errorConfig = ErrorConfig.getInstance();
	
	private Map<HttpUrlConfig, HttpUrlMethod> urlConfigCache = new HashMap<>();
	private Map<String, HttpUrlMethod> urlCache = new HashMap<>();
	private Set<Class<?>> clazzSet = new HashSet<>();

	private static HttpParser instance = new HttpParser();

	static
	{
		paramSet.add(String.class);
		paramSet.add(HttpRequest.class);
		paramSet.add(UploadFile.class);
		
		StringBuilder sb = new StringBuilder(32);
		for (Class<?> clazz : paramSet)
		{
			sb.append(clazz.getName()).append(",");
		}
		if (sb.length() > 1)
			sb.delete(sb.length() - 1, sb.length());
		//System.out.println(sb);
		paramString = sb.toString();
	}
	
	private HttpParser()
	{
	}

	public static HttpParser getInstance()
	{
		return instance;
	}

	public void setApplicationContext(ApplicationContext applicationContext)
	{
		this.applicationContext = applicationContext;
	}

	public void parse(Class<?> clazz)
	{
		if (clazzSet.contains(clazz))
			return;

		Http annoHttp = clazz.getAnnotation(Http.class);
		if (annoHttp == null)
			throw new IllegalArgumentException(clazz.getName() + " 缺少 @Http 注解");

		for (Method method : clazz.getDeclaredMethods())
		{
			if (method.getModifiers() != Modifier.PUBLIC)
				continue;
			
			if (method.getParameterCount() != 1)
				throw new IllegalArgumentException(method + " 只能有一个参数");
			
			Class<?> paramClazz = method.getParameterTypes()[0];
			if (!paramSet.contains(paramClazz))
				throw new IllegalArgumentException(method + " 的参数只能是：" + paramString);
			
			this.parseMethod(method, annoHttp);
		}

		clazzSet.add(clazz);
	}

	private void parseMethod(Method method, Http annoHttp)
	{
		Set<HttpMethod> httpMethodSet = new HashSet<>();
		this.addHttpMethod(httpMethodSet, annoHttp.method());
		
		Charset charset = annoHttp.charset();
		String contentType = annoHttp.contentType();
		StringBuilder url = new StringBuilder(32);
		if (!annoHttp.url().startsWith("/"))
			url.append("/");
		url.append(annoHttp.url());

		Http annoMethod = method.getAnnotation(Http.class);
		if (annoMethod != null) // 方法配置覆盖类配置
		{
			charset = annoMethod.charset();
			contentType = annoMethod.contentType();
			httpMethodSet.clear();
			this.addHttpMethod(httpMethodSet, annoMethod.method());
			if (!annoMethod.url().isEmpty() && !annoMethod.url().startsWith("/"))
				url.append("/");
			url.append(annoMethod.url());
		}
		
		if (httpMethodSet.isEmpty()) // 没有关联httpMethod
		{
			if (urlCache.containsKey(url.toString()))
				throw new IllegalArgumentException("已经存在：url=" + url);
			
			urlCache.put(url.toString(), new HttpUrlMethod(charset, method, contentType));
			logger.info("找到 Http 服务：{} [{}] [{}] [{}]，{}.{}()", url, method,
					charset.name(), contentType, method.getDeclaringClass().getSimpleName(), method.getName());
		}
		else
		{
			for (HttpMethod httpMethod : httpMethodSet)
			{
				HttpUrlConfig config = new HttpUrlConfig(url.toString(), httpMethod);
				if (urlConfigCache.containsKey(config))
					throw new IllegalArgumentException("已经存在：" + config);
				
				urlConfigCache.put(config, new HttpUrlMethod(charset, method, contentType));
				logger.info("找到 Http 服务：{} [{}] [{}] [{}]，{}.{}()", config.getUrl(), config.getMethod().name(),
						charset.name(), contentType, method.getDeclaringClass().getSimpleName(), method.getName());
			}
			
		}
		
	}
	
	private void addHttpMethod(Set<HttpMethod> httpMethodSet, HttpMethod[] httpMethods)
	{
		if (httpMethods == null || httpMethods.length == 0)
			return;
		
		for (HttpMethod httpMethod : httpMethods)
		{
			httpMethodSet.add(httpMethod);
		}
	}
	
	/**
	 * 获取 URL 对应方法配置
	 * 
	 * @param url URL
	 * @param method HTTP调用方法
	 * @return 方法配置
	 */
	public HttpUrlMethod getUrlMethod(String url, HttpMethod method)
	{

		HttpUrlConfig config = new HttpUrlConfig(url, method);
		HttpUrlMethod urlMethod = urlConfigCache.get(config);
		if (urlMethod == null)
		{
			urlMethod = urlCache.get(url);
		}
		return urlMethod;
	}

	/**
	 * 调用方法
	 * 
	 * @param request HTTP请求参数
	 * @return 方法返回
	 * @throws Exception
	 */
	public Object invoke(HttpRequest request) throws Throwable
	{
		HttpUrlMethod method = this.getUrlMethod(request.getUrl(), request.getHttpMethod());
		return this.invoke(request, method);
	}

	/**
	 * 调用方法
	 *
	 * @param request HTTP请求参数
	 * @param method UrlMethod
	 * @return 方法返回
	 * @throws Exception
	 */
	public Object invoke(HttpRequest request, HttpUrlMethod method) throws Throwable
	{
		if (method == null)
			throw errorConfig.getException(ErrorConfig.NOT_FOUND);

		Method m = method.getMethod();
		Object object = applicationContext.getBean(m.getDeclaringClass());

		Object param = request;
		Class<?> paramClazz = m.getParameterTypes()[0];
		if (paramClazz == String.class)
			param = request.getContent();
		else if (paramClazz == UploadFile.class)
			param = request.getFile();

		try
		{
			Object value = m.invoke(object, param);
			return value;
		}
		catch (Throwable e)
		{
			if (e instanceof InvocationTargetException)
				e = e.getCause();

			throw e;
		}
	}

}
